﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Net;
using System.Xml.Serialization;
using DTBuilderLib;
using System.IO;

namespace DeepZoomSLUtilities
{
    /// <summary>
    /// Code was loaded from the output of the Composer.
    /// </summary>
    public partial class DeepZoomer : UserControl
    {
        //
        // Based on prior work done by Lutz Gerhard, Peter Blois, and Scott Hanselman
        //

        double zoom = 1;
        bool duringDrag = false;
        bool mouseDown = false;
        Point lastMouseDownPos = new Point();
        Point lastMousePos = new Point();
        Point lastMouseViewPort = new Point();

        #region Dependency Properties
        /// <summary>
        /// Where we go to look for the image source -- hooked up so we can use this as a routed event, etc.
        /// </summary>
        public string Source
        {
            get { return (string)GetValue(SourceProperty); }
            set { SetValue(SourceProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Source.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SourceProperty =
            DependencyProperty.Register("Source", typeof(string), typeof(DeepZoomer), new PropertyMetadata((d, e) => ((DeepZoomer)d).UpdateSource()));

        /// <summary>
        /// The source file has changed. Propagate the change.
        /// </summary>
        void UpdateSource()
        {
            msi.Source = new DeepZoomImageTileSource(new Uri(Source, UriKind.Relative));
        }

        #endregion

        public double ZoomFactor
        {
            get { return zoom; }
            set { zoom = value; }
        }

        public DeepZoomer()
        {
            InitializeComponent();

            //
            // Firing an event when the MultiScaleImage is Loaded
            //
            this.msi.Loaded += new RoutedEventHandler(msi_Loaded);

            //
            // Firing an event when all of the images have been Loaded
            //
            this.msi.ImageOpenSucceeded += new RoutedEventHandler(msi_ImageOpenSucceeded);

            //
            // Handling all of the mouse and keyboard functionality
            //
            this.MouseMove += delegate(object sender, MouseEventArgs e)
            {
                lastMousePos = e.GetPosition(msi);

                if (duringDrag)
                {
                    Point newPoint = lastMouseViewPort;
                    newPoint.X += (lastMouseDownPos.X - lastMousePos.X) / msi.ActualWidth * msi.ViewportWidth;
                    newPoint.Y += (lastMouseDownPos.Y - lastMousePos.Y) / msi.ActualWidth * msi.ViewportWidth;
                    msi.ViewportOrigin = newPoint;
                }
            };

            this.MouseLeftButtonDown += delegate(object sender, MouseButtonEventArgs e)
            {
                lastMouseDownPos = e.GetPosition(msi);
                lastMouseViewPort = msi.ViewportOrigin;

                mouseDown = true;

                msi.CaptureMouse();
            };

            this.MouseLeftButtonUp += delegate(object sender, MouseButtonEventArgs e)
            {
                if (!duringDrag)
                {
                    bool shiftDown = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
                    double newzoom = zoom;

                    if (shiftDown)
                    {
                        newzoom /= 2;
                    }
                    else
                    {
                        newzoom *= 2;
                    }

                    Zoom(newzoom, msi.ElementToLogicalPoint(this.lastMousePos));
                }
                duringDrag = false;
                mouseDown = false;

                msi.ReleaseMouseCapture();
            };

            this.MouseMove += delegate(object sender, MouseEventArgs e)
            {
                lastMousePos = e.GetPosition(msi);
                if (mouseDown && !duringDrag)
                {
                    duringDrag = true;
                    double w = msi.ViewportWidth;
                    Point o = new Point(msi.ViewportOrigin.X, msi.ViewportOrigin.Y);
                    msi.UseSprings = false;
                    msi.ViewportOrigin = new Point(o.X, o.Y);
                    msi.ViewportWidth = w;
                    zoom = 1/w;
                    msi.UseSprings = true;
                }

                if (duringDrag)
                {
                    Point newPoint = lastMouseViewPort;
                    newPoint.X += (lastMouseDownPos.X - lastMousePos.X) / msi.ActualWidth * msi.ViewportWidth;
                    newPoint.Y += (lastMouseDownPos.Y - lastMousePos.Y) / msi.ActualWidth * msi.ViewportWidth;
                    msi.ViewportOrigin = newPoint;
                }
            };

            new MouseWheelHelper(this).Moved += delegate(object sender, MouseWheelEventArgs e)
            {
                e.Handled = true;

                double newzoom = zoom;

                if (e.Delta < 0)
                    newzoom /= 1.3;
                else
                    newzoom *= 1.3;

                Zoom(newzoom, msi.ElementToLogicalPoint(this.lastMousePos));
                msi.CaptureMouse();
            };
        }

        /// <summary>
        /// The MSI collection has shown up. Load up the web file that contains our meta data
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void msi_ImageOpenSucceeded(object sender, RoutedEventArgs e)
        {
            WebClient c = new WebClient();
            c.BaseAddress = c.BaseAddress.Substring(0, c.BaseAddress.LastIndexOf("/") + 1);
            c.DownloadStringCompleted += new DownloadStringCompletedEventHandler(c_DownloadStringCompleted);

            string reluri = Source.Substring(0, Source.LastIndexOf("/"));

            Uri u = new Uri(reluri + "/meetingMetadata.xml", UriKind.Relative);
            c.DownloadStringAsync(u);
        }

        /// <summary>
        /// The image meta data for the image we are looking at.
        /// </summary>
        private DZTalkVisualMetaData[] _imageMetaData;

        void c_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                ///
                /// Things have gone very wrong. We should get out of here!
                /// 

                return;
            }

            ///
            /// Deseralize the information into something we can deal with
            /// 

            XmlSerializer ser = new XmlSerializer(typeof(DZTalkVisualMetaData[]));
            StringReader r = new StringReader(e.Result);
            _imageMetaData = ser.Deserialize(r) as DZTalkVisualMetaData[];

            ///
            /// Now, update the collection with the information that is in the image meta data.
            /// 

            if (_imageMetaData.Length != msi.SubImages.Count)
            {
                return;
            }

            int i = 0;
            foreach (var subImage in msi.SubImages)
            {
                DZTalkVisualMetaData md = _imageMetaData[i];
                subImage.ViewportWidth = 1.0 / md.Width;
                subImage.ViewportOrigin = new Point(-md.X / md.Width, -md.Y / md.Width);
                subImage.ZIndex = md.Z;
                subImage.Opacity = 1;
                i = i + 1;
            }

            msi.ViewportWidth = 1;
        }

        void msi_Loaded(object sender, RoutedEventArgs e)
        {
            // Hook up any events you want when the image has successfully been opened
        }

        private void Zoom(double newzoom, Point p)
        {
            if (newzoom < 0.5)
            {
                newzoom = 0.5;
            }

            msi.ZoomAboutLogicalPoint(newzoom / zoom, p.X, p.Y);
            zoom = newzoom;
        }

        private void ZoomInClick(object sender, System.Windows.RoutedEventArgs e)
        {
            Zoom(zoom * 1.3, msi.ElementToLogicalPoint(new Point(.5 * msi.ActualWidth, .5 * msi.ActualHeight)));
        }

        private void ZoomOutClick(object sender, System.Windows.RoutedEventArgs e)
        {
            Zoom(zoom / 1.3, msi.ElementToLogicalPoint(new Point(.5 * msi.ActualWidth, .5 * msi.ActualHeight)));
        }

        private void GoHomeClick(object sender, System.Windows.RoutedEventArgs e)
        {
        	this.msi.ViewportWidth = 1;
			this.msi.ViewportOrigin = new Point(0,0);
            ZoomFactor = 1;
        }

        private void GoFullScreenClick(object sender, System.Windows.RoutedEventArgs e)
        {
        	if (!Application.Current.Host.Content.IsFullScreen) 
			{ 
				Application.Current.Host.Content.IsFullScreen = true; 
			} 
			else 
			{ 
				Application.Current.Host.Content.IsFullScreen = false; 
			} 
        }

        // Handling the VSM states
        private void LeaveMovie(object sender, System.Windows.Input.MouseEventArgs e)
        {
            VisualStateManager.GoToState(this, "FadeOut", true);
        }

        private void EnterMovie(object sender, System.Windows.Input.MouseEventArgs e)
        {
            VisualStateManager.GoToState(this, "FadeIn", true);
        }


        // unused functions that show the inner math of Deep Zoom
        public Rect getImageRect()
        {
            return new Rect(-msi.ViewportOrigin.X / msi.ViewportWidth, -msi.ViewportOrigin.Y / msi.ViewportWidth, 1 / msi.ViewportWidth, 1 / msi.ViewportWidth * msi.AspectRatio);
        }

        public Rect ZoomAboutPoint(Rect img, double zAmount, Point pt)
        {
            return new Rect(pt.X + (img.X - pt.X) / zAmount, pt.Y + (img.Y - pt.Y) / zAmount, img.Width / zAmount, img.Height / zAmount);
        }

        public void LayoutDZI(Rect rect)
        {
            double ar = msi.AspectRatio;
            msi.ViewportWidth = 1 / rect.Width;
            msi.ViewportOrigin = new Point(-rect.Left / rect.Width, -rect.Top / rect.Width);
        }
#if false
        public DeepZoomer()
        {
            InitializeComponent();

            ///
            /// When the image is loaded we are going to need to deal with a few things
            /// 

            msi.Loaded += new RoutedEventHandler(msi_Loaded);
            msi.ImageOpenSucceeded += new RoutedEventHandler(msi_ImageOpenSucceeded);

            ///
            /// Mouse controls
            /// 

            DefaultZoomFactor = 1.3;
        }

        void msi_ImageOpenSucceeded(object sender, RoutedEventArgs e)
        {
            throw new NotImplementedException();
        }

        void msi_Loaded(object sender, RoutedEventArgs e)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// The zoom factor we should start at. 1.3 is the
        /// arbitrary default...
        /// </summary>
        public double DefaultZoomFactor { get; set; }

        /// <summary>
        /// What the current zoom factor is.
        /// </summary>
        public double CurrentTotalZoom { get; set; }

        /// <summary>
        /// Don't let the user get too zeep. Zero means no max.
        /// </summary>
        public double MaxZoomIn { get; set; }

        /// <summary>
        /// How far in can the user zoom? This prevents one from getting so far
        /// out of the image that you can't see it any longer (it becomes too small).
        /// zero means no min.
        /// </summary>
        public double MaxZoomOut { get; set; }

        /// <summary>
        /// Performs a Zoom operation relative to where Image is at. 
        /// Example, call DoZoom twice with a Zoom of 1.25 will lead to an image that is zoomed at 
        /// 1.25 after first time and ( 1.25 * 1.25 for second time, which is a 1.56
        /// </summary>
        /// <param name="relativeZoom"> new zoom level; this is a RELATIVE value not absolute.</param>
        /// <param name="elementPoint"></param>
        private void DoZoom(double relativeZoom, Point elementPoint)
        {
            if ((MaxZoomOut != 0.0 && CurrentTotalZoom * relativeZoom < MaxZoomOut) ||
                  (MaxZoomIn != 0.0 && CurrentTotalZoom * relativeZoom > MaxZoomIn))
            {
                return;
            }

            ///
            /// Calculate the new zoom
            ///

            Point p = msi.ElementToLogicalPoint(elementPoint);
            msi.ZoomAboutLogicalPoint(relativeZoom, p.X, p.Y);
            ///this.Zoom = relativeZoom;
            CurrentTotalZoom *= relativeZoom;
        }

        /// <summary>
        /// Keep track of where the mouse is. In the local coordinates of the multiscale image control.
        /// </summary>
        private Point _lastMousePosition;

        /// <summary>
        /// The mouse has moved over the deep zoom control. Keep track.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
        }
        private void DeepZoom_MouseMove(object sender, MouseEventArgs e)
        {
            if (_isDragging)
            {
                Point newViewport = _lastDragViewportOrigin;
                Point currentMousePosition = e.GetPosition(msi);
                newViewport.X += (_lastMousePosition.X - currentMousePosition.X) / this.msi.ActualWidth * this.msi.ViewportWidth;
                newViewport.Y += (_lastMousePosition.Y - currentMousePosition.Y) / this.msi.ActualWidth * this.msi.ViewportWidth;
                this.msi.ViewportOrigin = newViewport;
                _lastDragViewportOrigin = newViewport;
            }
            _lastMousePosition = e.GetPosition(msi);
        }

        /// <summary>
        /// Finish up all of our wiring of events.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            /// Track our mouse wheel.
            MouseWheelHelper helper = new MouseWheelHelper(this);
            helper.Moved += new EventHandler<MouseWheelEventArgs>(helper_Moved);
        }

        /// <summary>
        /// The mouse wheel has been moved.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void helper_Moved(object sender, MouseWheelEventArgs e)
        {
            /// Zoom in or out.
            if (e.Delta > 0)
            {
                DoZoom(DefaultZoomFactor, _lastMousePosition);
            }
            else
            {
                DoZoom(1.0 / DefaultZoomFactor, _lastMousePosition);
            }
        }

        /// <summary>
        /// Are we panning the image right now?
        /// </summary>
        private bool _isDragging = false;

        /// <summary>
        /// Where was the last point we were showing in the upper left of this control?
        /// </summary>
        private Point _lastDragViewportOrigin;

        /// <summary>
        /// The user wishes to pan our image.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UserControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            _lastDragViewportOrigin = msi.ViewportOrigin;
            _lastMousePosition = e.GetPosition(msi);
            _isDragging = true;
        }

        /// <summary>
        /// User is done with the pan operation.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UserControl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            _isDragging = false;
        }

        /// <summary>
        /// If we are dragging and we leave, we should get out...
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UserControl_MouseLeave(object sender, MouseEventArgs e)
        {
            _isDragging = false;
            msi.Cursor = Cursors.Arrow;
        }

        /// <summary>
        /// Move the mouse -- if we are dragging, then pan!
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UserControl_MouseMove(object sender, MouseEventArgs e)
        {
        }
#endif

    }

}
